#ifndef __DMA_H
#define __DMA_H

#include "./rcc.h"

namespace stm32f1::dma {

#define DMA_INSTANCE(BASE) reinterpret_cast<DMA_TypeDef *>(BASE)
#define CHAN_INSTANCE(BASE) reinterpret_cast<DMA_Channel_TypeDef *>(BASE)

template <uint32_t BASE, uint32_t FIELD>
struct Dma : public rcc::AHBBus<FIELD> {
    static constexpr uint32_t BASE_VAL = BASE;
};

#define DMA_FLAGS(X)                                                           \
    DMA_IFCR_CGIF##X | DMA_IFCR_CTCIF##X | DMA_IFCR_CHTIF##X | DMA_IFCR_CTEIF##X

template <typename DMA, uint32_t BASE, uint32_t CHAN>
struct DmaChan : public DMA {

    static constexpr uint32_t CHAN_BASE_VAL = BASE;
    static constexpr uint32_t CHAN_VAL = CHAN;

    template <typename CFG, uint32_t PERIPH_ADDR>
    static inline void set_cfg(uint32_t buf_addr, uint16_t count) {
        CHAN_INSTANCE(BASE)->CCR = 0x00;
        CHAN_INSTANCE(BASE)->CMAR = buf_addr;
        CHAN_INSTANCE(BASE)->CPAR = PERIPH_ADDR;
        CHAN_INSTANCE(BASE)->CNDTR = count;
        CHAN_INSTANCE(BASE)->CCR = CFG::CCR;
    }

    static inline void clear_flags() {
        if constexpr (CHAN_VAL == 1) {
            DMA_INSTANCE(DMA::BASE_VAL)->IFCR = DMA_FLAGS(1);
        } else if constexpr (CHAN_VAL == 2) {
            DMA_INSTANCE(DMA::BASE_VAL)->IFCR = DMA_FLAGS(2);
        } else if constexpr (CHAN_VAL == 3) {
            DMA_INSTANCE(DMA::BASE_VAL)->IFCR = DMA_FLAGS(3);
        } else if constexpr (CHAN_VAL == 4) {
            DMA_INSTANCE(DMA::BASE_VAL)->IFCR = DMA_FLAGS(4);
        } else if constexpr (CHAN_VAL == 5) {
            DMA_INSTANCE(DMA::BASE_VAL)->IFCR = DMA_FLAGS(5);
        } else if constexpr (CHAN_VAL == 6) {
            DMA_INSTANCE(DMA::BASE_VAL)->IFCR = DMA_FLAGS(6);
        } else if constexpr (CHAN_VAL == 7) {
            DMA_INSTANCE(DMA::BASE_VAL)->IFCR = DMA_FLAGS(7);
        }
    }

    static inline uint16_t ndtr() {
        return CHAN_INSTANCE(CHAN_BASE_VAL)->CNDTR;
    }

    static inline bool is_tc() {
        if constexpr (CHAN_VAL == 1) {
            return (DMA_INSTANCE(DMA::BASE_VAL)->ISR & DMA_ISR_TCIF1) != 0;
        } else if constexpr (CHAN_VAL == 2) {
            return (DMA_INSTANCE(DMA::BASE_VAL)->ISR & DMA_ISR_TCIF2) != 0;
        } else if constexpr (CHAN_VAL == 3) {
            return (DMA_INSTANCE(DMA::BASE_VAL)->ISR & DMA_ISR_TCIF3) != 0;
        } else if constexpr (CHAN_VAL == 4) {
            return (DMA_INSTANCE(DMA::BASE_VAL)->ISR & DMA_ISR_TCIF4) != 0;
        } else if constexpr (CHAN_VAL == 5) {
            return (DMA_INSTANCE(DMA::BASE_VAL)->ISR & DMA_ISR_TCIF5) != 0;
        } else if constexpr (CHAN_VAL == 6) {
            return (DMA_INSTANCE(DMA::BASE_VAL)->ISR & DMA_ISR_TCIF6) != 0;
        } else if constexpr (CHAN_VAL == 7) {
            return (DMA_INSTANCE(DMA::BASE_VAL)->ISR & DMA_ISR_TCIF7) != 0;
        }
    }
};

struct UartWriteCfg {
    static constexpr uint32_t CCR =
        DMA_CCR1_MINC | DMA_CCR1_DIR | DMA_CCR1_TCIE | DMA_CCR1_EN;
};

struct UartRecvCfg {
    static constexpr uint32_t CCR = DMA_CCR1_MINC | DMA_CCR1_CIRC | DMA_CCR1_EN;
};

struct AdcCfg {
    static constexpr uint32_t CCR = (0x01 << 10) + (0x01 << 8) + DMA_CCR1_MINC +
                                    DMA_CCR1_CIRC + DMA_CCR1_EN;

    // static constexpr uint32_t CCR = 0x0521 + 0x80;
};

using Dma1 = Dma<DMA1_BASE, RCC_AHBENR_DMA1EN>;
//using Dma2 = Dma<DMA2_BASE, RCC_AHBENR_DMA2EN>;

using Dma1Ch1 = DmaChan<Dma1, DMA1_Channel1_BASE, 1>;
using Dma1Ch2 = DmaChan<Dma1, DMA1_Channel2_BASE, 2>;
using Dma1Ch3 = DmaChan<Dma1, DMA1_Channel3_BASE, 3>;
using Dma1Ch4 = DmaChan<Dma1, DMA1_Channel4_BASE, 4>;
using Dma1Ch5 = DmaChan<Dma1, DMA1_Channel5_BASE, 5>;
using Dma1Ch6 = DmaChan<Dma1, DMA1_Channel6_BASE, 6>;
using Dma1Ch7 = DmaChan<Dma1, DMA1_Channel7_BASE, 7>;

//using Dma2Ch1 = DmaChan<Dma2, DMA2_Channel1_BASE, 1>;
//using Dma2Ch2 = DmaChan<Dma2, DMA2_Channel2_BASE, 2>;
//using Dma2Ch3 = DmaChan<Dma2, DMA2_Channel3_BASE, 3>;
//using Dma2Ch4 = DmaChan<Dma2, DMA2_Channel4_BASE, 4>;
//using Dma2Ch5 = DmaChan<Dma2, DMA2_Channel5_BASE, 5>;

template <uint32_t BASE> struct DmaSel {};

template <> struct DmaSel<USART1_BASE> {
    using TX_CHAN = Dma1Ch4;
    using RX_CHAN = Dma1Ch5;
};

template <> struct DmaSel<USART2_BASE> {
    using TX_CHAN = Dma1Ch7;
    using RX_CHAN = Dma1Ch6;
};

template <> struct DmaSel<USART3_BASE> {
    using TX_CHAN = Dma1Ch2;
    using RX_CHAN = Dma1Ch3;
};

//template <> struct DmaSel<UART4_BASE> {
//    using TX_CHAN = Dma2Ch5;
//    using RX_CHAN = Dma2Ch3;
//};

template <> struct DmaSel<ADC1_BASE> {
    using CHAN = Dma1Ch1;
};

//template <> struct DmaSel<ADC3_BASE> {
//    using CHAN = Dma2Ch5;
//};

} // namespace stm32f1::dma

#endif
